package jsonmap

import (
	"errors"
	"gitee.com/zhongguo168a/gocodes/datax/schemax/fieldkind"
	"reflect"
	"strconv"
)

var (
	refNil = reflect.ValueOf(nil)
)

func MapToStruct(from map[string]interface{}, to interface{}) (err error) {
	if from == nil {
		return
	}
	if to == nil {
		return
	}
	err = structFromMapObj(reflect.ValueOf(to), nil, from)
	if err != nil {
		return err
	}

	return
}

func structFromMapObj(sourceRef reflect.Value, sourceTyp reflect.Type, itargetMap interface{}) (err error) {
	var isTargetNil bool
	targetMap, ok := itargetMap.(map[string]interface{})
	if ok {
		if targetMap == nil {
			isTargetNil = true
		}
	} else {
		isTargetNil = true
	}

	if sourceRef.Kind() == reflect.Ptr {
		if isTargetNil {
			sourceRef.Set(refNil)
			return
		}
		if sourceTyp != nil {
			sourceTyp = sourceTyp.Elem()
		}
		if sourceRef.IsNil() {
			if sourceTyp == nil {
				return // 无法创建新的对象，返回
			}
			sourceRef.Set(reflect.New(sourceTyp))
		}
		sourceRef = sourceRef.Elem()

	} else {
		if isTargetNil {
			return
		}
	}

	if sourceTyp == nil {
		sourceTyp = sourceRef.Type()
	}
	for i := 0; i < sourceTyp.NumField(); i++ {
		field := sourceTyp.Field(i)
		jsonzTagStr := field.Tag.Get("jsonz")
		if jsonzTagStr == "_" {
			continue
		}
		//tags := parseTag(jsonzTagStr)
		fname := field.Name
		lastName := func() (x string) {
			jsontag := field.Tag.Get("alias")
			x = jsontag
			if x == "" {
				x = fname
			}
			return
		}()
		valRef := sourceRef.Field(i)
		ftyp := field.Type
		catalog := fieldkind.ReflectToFieldKind(ftyp)

		tval, thas := targetMap[lastName]
		if catalog == fieldkind.Class && field.Anonymous {

		} else {
			if !thas { // 如果目标map不存在字段，则不作任何动作
				continue
			}
		}

		switch catalog {
		case fieldkind.Class:
			if field.Anonymous {
				err = structFromMapObj(valRef, ftyp, targetMap)
				if err != nil {
					err = errors.New(err.Error() + " at class field=" + field.Name)
					return
				}
			} else {
				err = structFromMapObj(valRef, ftyp, tval)
				if err != nil {
					err = errors.New(err.Error() + " at class field=" + field.Name)
					return
				}
			}
		case fieldkind.Array:
			err = structFromMapArray(valRef, ftyp, tval)
			if err != nil {
				err = errors.New(err.Error() + " at class field=" + field.Name)
				return
			}
		case fieldkind.Map:
			err = structFromMapMap(valRef, tval, ftyp)
			if err != nil {
				err = errors.New(err.Error() + " at class field=" + field.Name)
				return
			}
		case fieldkind.Enum:
			err = structFromMapEnum(ftyp, valRef, tval)
			if err != nil {
				err = errors.New(err.Error() + " at class field=" + field.Name)
				return
			}
		case fieldkind.Basic:
			err = structFromMapBasic(ftyp, valRef, ConvertJsonKeyToReflectKind(ftyp.Kind(), tval))
			if err != nil {
				err = errors.New(err.Error() + " at class field=" + field.Name)
				return
			}
		case fieldkind.Any:
			r, anyerr := structFromMapAny(ftyp, valRef, tval)
			if anyerr != nil {
				err = errors.New(anyerr.Error() + " at class field=" + field.Name)
				return
			}
			valRef.Set(r)
		default:
			err = errors.New("not support: " + ftyp.String())
			return

		}
	}
	return
}

func structFromMapAny(ftyp reflect.Type, val reflect.Value, target interface{}) (r reflect.Value, err error) {
	switch tval := target.(type) {
	case map[string]interface{}:
		r = reflect.ValueOf(target)
	default:
		target = InterfaceToJSONValue(tval)
		r = reflect.ValueOf(target)
	}
	return
}

func structFromMapEnum(ftyp reflect.Type, val reflect.Value, target interface{}) (err error) {
	tarref := reflect.ValueOf(target)
	if tarref.Type().String() != ftyp.String() {
		if !tarref.Type().ConvertibleTo(ftyp) {
			err = errors.New("cannot convert to " + ftyp.String())
			return
		}

		tarref = tarref.Convert(ftyp)
	}
	val.Set(tarref)
	return
}

func structFromMapBasic(basic reflect.Type, val reflect.Value, target interface{}) (err error) {
	if !reflect.ValueOf(target).Type().ConvertibleTo(basic) {
		err = errors.New("cannot convert to " + basic.String())
		return
	}
	val.Set(reflect.ValueOf(target).Convert(basic))
	return
}

func structFromMapArray(sourceArr reflect.Value, ftyp reflect.Type, itarget interface{}) (err error) {
	var isTargetNil bool
	targetArr, ok := itarget.([]interface{})
	if ok {
		if targetArr == nil {
			isTargetNil = true
		}
	} else {
		isTargetNil = true
	}
	if isTargetNil {
		// todo: 将数组设置成nil？
		//sourceArr.Set(refNil)
		return
	}

	elemTyp := ftyp.Elem()

	sourceArr.Set(reflect.MakeSlice(ftyp, len(targetArr), cap(targetArr)))
	catalog := fieldkind.ReflectToFieldKind(elemTyp)
	switch catalog {
	case fieldkind.Class:
		for i := 0; i < sourceArr.Len(); i++ {
			classerr := structFromMapObj(sourceArr.Index(i), elemTyp, targetArr[i])
			if classerr != nil {
				err = errors.New("at index=" + strconv.Itoa(i) + ": " + classerr.Error())
				return
			}
		}
	case fieldkind.Basic:
		for i := 0; i < sourceArr.Len(); i++ {
			err = structFromMapBasic(elemTyp, sourceArr.Index(i), targetArr[i])
			if err != nil {
				err = errors.New("at index=" + strconv.Itoa(i) + ": " + err.Error())
				return
			}

		}
	case fieldkind.Any:
		for i := 0; i < sourceArr.Len(); i++ {
			elemVal := sourceArr.Index(i)
			r, anyerr := structFromMapAny(ftyp, elemVal, targetArr[i])
			if anyerr != nil {
				err = errors.New("at index=" + strconv.Itoa(i) + ": " + err.Error())
				return
			}
			elemVal.Set(r)
		}
	default:
		err = errors.New("not support: " + elemTyp.String())
		return
	}
	return
}

func structFromMapMap(sourceRef reflect.Value, itarget interface{}, ftyp reflect.Type) (err error) {
	var isTargetNil bool
	targetMap, ok := itarget.(map[string]interface{})
	if ok {
		if targetMap == nil {
			isTargetNil = true
		}
	} else {
		isTargetNil = true
	}
	if isTargetNil {
		if !sourceRef.IsNil() {
			sourceRef.Set(refNil)
		}
		return
	}
	// 如果 map存在，对象不存在，初始化对象
	if sourceRef.IsNil() {
		sourceRef.Set(reflect.MakeMap(ftyp))
	}

	keytyp := ftyp.Key()
	vtyp := ftyp.Elem()
	for tkey, tval := range targetMap {
		ckey := reflect.ValueOf(ConvertJsonKeyToReflectKind(ftyp.Key().Kind(), tkey))
		ckey = ckey.Convert(keytyp)
		valRef := sourceRef.MapIndex(ckey)
		catalog := fieldkind.ReflectToFieldKind(vtyp)
		switch catalog {
		case fieldkind.Class:
			err = structFromMapObj(valRef, vtyp, tval)
			if err != nil {
				err = errors.New(err.Error() + "at map key=" + ckey.String())
				return
			}
		case fieldkind.Enum:
			err = structFromMapEnum(vtyp, valRef, tval)
			if err != nil {
				err = errors.New(err.Error() + "at map key=" + ckey.String())
				return
			}
		case fieldkind.Basic:
			sourceRef.SetMapIndex(ckey, reflect.ValueOf(tval).Convert(vtyp))
		case fieldkind.Any:
			r, anyerr := structFromMapAny(ftyp, valRef, tval)
			if anyerr != nil {
				err = errors.New(anyerr.Error() + " at map key=" + ckey.String())
				return
			}
			sourceRef.SetMapIndex(ckey, r)

		default:
			err = errors.New("not support: " + vtyp.String())
			return
		}
	}

	return
}
